﻿if (!JSON) {
	throw new Error("JSON library is required to load PSB. Please reference json2.js");
}

if (!jQuery) {
	throw new Error("jQuery library is required to load PSB");
}

String.format = function (text) {
	'use strict';
	if (arguments.length <= 1) return text;
	var length = arguments.length - 2, token;
	for (token = 0; token <= length; token++) {
		text = text.replace(new RegExp("\\{" + token + "\\}", "gi"), arguments[token + 1]);
	}
	return text;
};

//Keeping old functionality due to heavy usage
if (jQuery && !jQuery.browser) {
	(function (fn) {
		var matched, browser;
		matched = (function () {
			var ua = navigator.userAgent.toLowerCase();
			var match = /(chrome)[ \/]([\w.]+)/.exec(ua) ||
			/(webkit)[ \/]([\w.]+)/.exec(ua) ||
			/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) ||
			/(msie) ([\w.]+)/.exec(ua) ||
			ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) ||
			[];

			return {
				browser: match[1] || "",
				version: match[2] || "0"
			};
		})();
		browser = {};

		if (matched.browser) {
			browser[matched.browser] = true;
			browser.version = matched.version;
		}

		// Chrome is Webkit, but Webkit is also Safari.
		if (browser.chrome) {
			browser.webkit = true;
		} else if (browser.webkit) {
			browser.safari = true;
		}
		fn.browser = browser;
	})(jQuery);
}


//Enable cross domain request
jQuery.support.cors = true;

var PSB;

if (!PSB) PSB = {};


(function (fn) {
	'use strict';
	var hubcounter = 0,
		hubconnections = {};

	fn.protocols = {
		httpstreaming: 1,
		serversent: 4,
		ieforeverframe: 2,
		foreverframe: 3,
		longpolling: 5
	};

	fn.hub = function () {
		var self = this,
			interval = 500,
			transport = null,
			active = false,
			msgID = 0,
			hubID = ++hubcounter,
			name = "hub" + hubID,
			frameid = name + "frame",
			url = null,
			frameurl = null,
			protocol = fn.setting.protocol() ||
				(jQuery.browser.msie ? fn.protocols.foreverframe :
				jQuery.browser.opera ? fn.protocols.serversent :
				jQuery.browser.safari || jQuery.browser.webkit || jQuery.browser.mozilla ?
				fn.protocols.httpstreaming : fn.protocols.foreverframe),
			transportType = (
				protocol === fn.protocols.ieforeverframe || protocol === fn.protocols.foreverframe ? "foreverframe" :
				protocol === fn.protocols.httpstreaming ? "httpstreaming" :
				protocol === fn.protocols.serversent ? "serversent" : ""),
			scope = function (f, context) {
				return function () { return f.apply(context); };
			},
			xhrcallback = function () {
				if (transport.readyState === 4) { self.close(); return; }
				var messages = transport.responseText.match(/<comet>(.+?)<\/comet>/g);
				while (messages && messages[msgID]) {
					var message = messages[msgID].substring(7, messages[msgID].length - 8),
						msg = JSON.parse(message);
					self.onmessage(msg);
					msgID++;
				}
			},
			checkframe = function () {
				var iframe = transport.getElementById(name);
				if (!iframe) return;
				if (iframe.readyState === 'complete') { self.close(); return; }
				setTimeout(scope(checkframe, self), interval);
			};
		hubconnections[hubID] = self;
		var _open = function () {
			if (active) return;
			active = true;
			switch (protocol) {
				case fn.protocols.httpstreaming:
					transport = ("XDomainRequest" in window) ? new XDomainRequest() :
						new XMLHttpRequest();
					transport.open("GET", url, true);
					transport.onreadystatechange = scope(xhrcallback, self);
					transport.send(null);
					break;
				case fn.protocols.ieforeverframe:
					var p = document.location.protocol; 
					transport = new ActiveXObject("htmlfile");
					transport.open();
					transport.write(String.format("<html><script>{0}</script></html>",
						p.indexOf("file") >= 0 || document.domain.indexOf("localhost") >= 0 ? "" :
						String.format("document.domain='{0}';", document.domain)));
					transport.close();
					fn.addframehandler(frameid, self.onmessage);
					var div = transport.createElement("div");
					div.id = name + "div";
					transport.body.appendChild(div);
					div.innerHTML = String.format("<iframe id='{0}' src='{1}'></iframe>", frameid, frameurl);
					setTimeout(scope(checkframe, self), interval);
					break;
				case fn.protocols.foreverframe:
					transport = document.createElement("iframe");
					transport.id = frameid;
					transport.style.display = "none";
					document.body.appendChild(transport);
					fn.addframehandler(frameid, self.onmessage);
					transport.src = frameurl;
					break;
				case fn.protocols.serversent:
					transport = new EventSource(url);
					transport.onmessage = function (response) {
						var msg = JSON.parse(response.data);
						self.onmessage(msg);
					};
					break;
			}
			fn.onprotocolopen(self);
		};
		this.onclose = function () { };
		this.onmessage = function (msg) { };
		this.open = function (uri) {
			url = uri + "&transport=" + transportType;
			frameurl = url + "&frameid=" + frameid;
			setTimeout(function () { _open(); }, 50);
		};
		this.close = function () {
			if (!active) return;
			active = false;
			delete hubconnections[hubID];
			switch (protocol) {
				case fn.protocols.httpstreaming:
					if (transport.readyState !== 4) transport.abort();
					break;
				case fn.protocols.ieforeverframe:
					var transportdiv = transport.getElementById(name + "div"),
						transportframe = transport.getElementById(name);
					transportframe.contentWindow.document.location.replace("about:blank");
					jQuery(transportdiv).remove(transportframe);
					jQuery(transport.body).remove(transportdiv);
					transport.parentWindow.push = null;
					break;
				case fn.protocols.foreverframe:
					var content = transport.contentWindow || transport.contentDocument;
					content.document.location.replace("about:blank");
					transport.push = null;
					jQuery(document.body).remove(transport);
					break;
				case fn.protocols.serversent:
					transport.close();
					break;
			}
			transport = null;
			if(jQuery.browser.msie && CollectGarbage) CollectGarbage();
			self.onclose(self);
		};
	};
} (PSB));

(function (fn) {
	'use strict';
	var endpointaddress = "http://localhost:8087/esb/",
		framehandlers = {},
		websocketaddress = "",
		websocketsubscriptionaddress = "",
		websocketport = "8081",
		websocketsubscriptionport = "8082",
		websocketrpc = true,
		rethrow = false,
		apikey = "demo",
		passcode = "demo",
		autoreconnect = true,
		protocol = undefined,
		includehttpheaders = false,
		usernamekey = "pservicebus_username",
		apikeytoken = "pservicebus_apikey_key",
		passcodetoken = "pservicebus_passcode_key",
		clienttoken = "",
		username = "",
		durable = false,
		handlers = {},
		topics = {},
		longpolling = false,
		servertimeout = 0,
		hasevent = false,
		getstorage = function (c_name) {
			if(window.localStorage){
				return window.localStorage[c_name];
			}
			var i, x, y, ARRcookies = document.cookie.split(";");
			for (i = 0; i < ARRcookies.length; i++) {
				x = ARRcookies[i].substr(0, ARRcookies[i].indexOf("="));
				y = ARRcookies[i].substr(ARRcookies[i].indexOf("=") + 1);
				x = x.replace(/^\s+|\s+$/g, "");
				if (x === c_name) {
					return unescape(y);
				}
			}
		},
		getorigin = function(){
			var tokens = endpointaddress.split("/");
			return [tokens[0], tokens[2]].join("//");
		},
		bindevent = function(el, eventName, eventHandler) {
			if(hasevent) return;
			if (el.addEventListener){
				el.addEventListener(eventName, eventHandler, false); 
			} else if (el.attachEvent){
				el.attachEvent('on'+eventName, eventHandler);
			}
			hasevent = true;
		},
		handleframe = function(evt){
			var origin = getorigin();
			if(evt.origin != origin) return;
			var tokens = evt.data.split("|||");
			var frameid = tokens[0];
			framehandlers[frameid](JSON.parse(tokens[1]));
		},
		setstorage = function (c_name, value, exdays) {
			if(window.localStorage){
				if(exdays <= -1)
					window.localStorage.removeItem(c_name);
				else
					window.localStorage[c_name] = value;
				return;
			}
			var exdate = new Date();
			exdate.setDate(exdate.getDate() + exdays);
			var c_value = escape(value) + ((!exdays) ? "" : "; expires=" + exdate.toUTCString());
			document.cookie = c_name + "=" + c_value;
		},
		loadscript = function (src, callback){
			var s = document.createElement("script");
			s.type = "text/javascript";
			if(s.onload){
				s.onload = function(){
					s.onload = null;
					if(callback) callback();
				}
			}else{
				s.onreadystatechange = function(){
					if(s.readyState === 'loaded' ||  s.readyState == "complete"){
						if(callback) callback();
					}
				};
			}
			s.src = src;
			document.getElementsByTagName("head")[0].appendChild(s);
		},
		getserver = function(){
			var url = endpointaddress.replace("//", "\\").split("/")[0];
			return url.replace("\\", "//") + "/flash/";
		},
		requires = function (callback){
			if(window.WebSocket) {
				if(callback) callback();
				return;
			};
		
			var server = getserver(),
				swfobjectjs = server + "swfobject.js",
				websocketjs = server + "web_socket.js";
			
			WEB_SOCKET_SWF_LOCATION = server + "WebSocketMainInsecure.swf";
		
			loadscript(swfobjectjs, function(){
				loadscript(websocketjs, callback);
			});
		},
		getusername = function () {
			if (username) return username;
			username = getstorage(usernamekey);
			if (!username){
				var date = new Date();
				username = String.format("js{0}{1}{2}", date.getTime(), date.getMonth(), date.getFullYear());
			}else { durable = true; }
			if (durable) setstorage(usernamekey, username, 30);
			return username;
		};
	fn.ondisconnect = function () { };
	fn.onprotocolopen = function(e) { };
	fn.onprotocolclose = function(e) { };
	fn.onprotocolretry = function(e) { };
	fn.onprotocolretryfail = function(e) { };
	fn.serverfail = function(e){
		if(fn.logging()) console.log(e);
	};
	fn.forceclose = function(msg){};
	fn.ondelete = function() {};
	fn.addframehandler = function(frameid, func){
		framehandlers[frameid] = func;
	};
	fn.transport = {
		msmq: 0,
		rabbitmq: 1,
		tcp: 2,
		redis: 6
	};
	fn.format = {
		json: 1,
		xml: 0
	};

	fn.timespan = function (day, hour, min, sec, ms) {
		sec = sec || 0;
		ms = ms || 0;
		min = min || 0;
		hour = hour || 0;
		day = day || 0;
		var date = new Date();
		date.setDate(date.getDate() + day);
		date.setMinutes(date.getMinutes() + min);
		date.setSeconds(date.getSeconds() + sec);
		date.setMilliseconds(date.getMilliseconds() + ms);
		date.setHours(date.getHours() + hour);
		return date.getTime() - (new Date().getTime());
	};

	fn.logging = function(){
		return fn.setting.logging === true;
	};

	fn.log = function(){
		if(!fn.logging()) return;
		if(console.log)
			console.log.apply(console, arguments);
	}

	var connector = (function () {
		var wscallbacks = {},
			failcallbacks = {},
			wscounter = 0,
			wsqueue = [],
			wsconnected = false,
			wslost = false,
			haswebsocket = !!window.WebSocket,
			websocket,
			wsupdatestate = function(){
				if(wsconnected == true || (websocket && (websocket.readyState == WebSocket.CLOSED || websocket.readyState == WebSocket.CLOSING))) 
					wslost = true;
				wsconnected = false;
				if(wslost === true){
					fn.log("Lost connection to websocket RPC. Falling back to ajax");
					for(var i = 0; i < wsqueue.length; i++){
						var json = wsqueue[i];
						var id = json.CorrelationID, callback, failcallback;
						if(id){
							callback = wscallbacks[id];
							failcallback = failcallbacks[id];
						}
						connector.helper.invoke(json.Name, json.Command, callback, failcallback, json.Async);
					} 
					wsqueue.length = 0;
				}
			},
			getws = function(failed){
				if(!haswebsocket) throw "WebSocket is not supported by your browser";
				if(websocket && (websocket.readyState != WebSocket.CLOSED && websocket.readyState != WebSocket.CLOSING)) 
					return websocket;
				fn.log("Connecting to websocket subscription server");
				websocket = new WebSocket(fn.setting.websocketsubscription());
				websocket.onopen = function(){
					websocket = this;
					wsconnected = true;
					wslost = false;
					fn.log("Websocket subscription is ready");
					for(var i = 0; i < wsqueue.length; i++){
						if(websocket.readyState == WebSocket.OPEN)
							websocket.send(JSON.stringify(wsqueue[i]));
					}
					wsqueue.length = 0;
				};
				websocket.onmessage = function(evt){
					var msg = evt.data,
						response = typeof(msg) == "string" ? JSON.parse(msg) : msg,
						id = response.CorrelationID,
						callback = id ? wscallbacks[id] : null,
						failcallback = id ? failcallbacks[id] : null;
					
					//Using psbka as KeepAlive in order to send less data
					if(response.psbka){
						//Do nothing
						fn.log("Received Websocket KeepAlive for Websocket-RPC");
						return;
					}

					if(callback){
						callback(response.Result);
						delete wscallbacks[id];
					}
					if(failcallback) {
						delete failcallbacks[id];
					}
				};
				websocket.onerror = function(error){
					wsupdatestate();
					if(failed) failed(error);
				};            
				websocket.onclose = function(){
					wsupdatestate();
				}
				return websocket;
			},
			wssend = function(message, failed){
				var conn = getws(failed);
				if(wsconnected) conn.send(JSON.stringify(message));
				else {
					if(wslost === true || (conn && (conn.readyState == WebSocket.CLOSED || conn.readyState == WebSocket.CLOSING)))
						throw "WebSocket is not available at this moment";
					wsqueue.push(message);
				}
			};

		return {
			helper: (function () {
				var usejsonp = false,
					fail = function (e) {
						var error = e;
						if (typeof (e) !== "string") error = JSON.stringify(e);
						if(fn.serverfail) fn.serverfail(error);
					},
					ws = function(method, data, success, failed, async){
						var conn, id = success ? (++wscounter).toString() : null;
						try{
							conn = getws(fail);
							if(success) wscallbacks[id] = success;
							if(failed) failcallbacks[id] = failed;

							var message = {
								ConnectionID: getusername(), Async: async,
								Name: method, UserName: apikey, Password: passcode, CorrelationID: id,
								Command: data, ClientToken: clienttoken
							};
							wssend(message, failed);
						}catch(e){
							if(id && wscallbacks[id]){
								delete wscallbacks[id];
							}
							if(id && failcallbacks[id]){
								delete failcallbacks[id];
							}
							json(method, data, success, failed, async);
						}
					},
					jsonp = function (method, data, success, failed) {
						data.ReThrowException = rethrow.toString();
						data.ESBUserName = apikey;
						data.ESBPassword = passcode;
						var url = String.format("{0}Get{1}?ConnectionID={2}&ClientToken={3}&callback=?", endpointaddress, method, getusername(), clienttoken);
						if(includehttpheaders) {
							url += "&includeHttpHeaders=true";
						}
						jQuery.ajax({
							url: url,
							data: data,
							dataType: 'jsonp',
							crossDomain: true,
							contentType: 'application/json; charset=utf-8',
							success: function (result) {
								var value = typeof(result) == "string" && (result.indexOf("[") >= 0 || result.indexOf("{") >= 0) 
									? JSON.parse(result) : result;
								if (success) success(value);
							},
							error: function (e) {
								if(failed) failed(e);
								else {
									if (fail) fail(e);
								}
							},
							timeout: servertimeout
						});
					},
					json = function (method, data, success, failed, async) {
						async = async || false;
						var url = String.format("{0}{1}?ReThrowException={2}&ESBUserName={3}&ESBPassword={4}&ConnectionID={5}&ClientToken={6}",
							endpointaddress, method, rethrow, apikey, passcode, getusername(), clienttoken);
						if(includehttpheaders) {
							url += "&includeHttpHeaders=true";
						}
						jQuery.ajax({
							type: 'POST',
							url: url,
							data: JSON.stringify(data),
							contentType: "application/json;charset=utf-8",
							async: async,
							success: function (result, status, xhr) {
								if (result) {
									var d = result.d || result;
									if (d) {
										var evald = typeof(d) == "string" && (d.indexOf("[") >= 0 || d.indexOf("{") >= 0)
										 ? JSON.parse(d) : d;
										if (success) success(evald);
									}
								}
							},
							error: function (xhr, e, msg) {
								if(failed) failed(xhr.responseText);
								else {
									if (fail) fail(xhr.responseText);
								}
								if (!usejsonp) {
									usejsonp = true;
									try{
										jsonp(method, data, success);
									} catch(e){
										if(failed) failed(e);
										else {
											if(fail) fail(e);
										}
									}
									return;
								}
							},
							timeout: servertimeout
						});
					}; 
				return {
					invoke: function(method, data, success, failed, async){
						data = data || {};
						//Use ajax for disconnect and delete to guarantee completion of request
						var usewebsocket = haswebsocket && websocketrpc === true && (method !== "DeleteSubscriber" && method !== "Disconnect");
						if(usewebsocket) ws(method, data, success, failed, async)
						else {
							try{
								if(usejsonp) jsonp(method, data, success, failed);
								else json(method, data, success, failed, async);
							} catch(e){
								if(failed) failed(e);
								else {
									if(fail) fail(e);
								}
							}
						}
					} 
				};
			})()
		};
	})();
	
	//Manage single websocket conneciton for all topic listener
	fn.websocketconnector = (function(){
		var websocket = null,
			callbacks = {},
			running = false,
			queue = [],
			isclosed = false,
			subscriptionmessages = {},
			websocketinit = function(retry){
				if(!websocket) return;
				running = true;
				while(queue.length > 0){
					var msg = queue.pop();
					send(msg);
				}
				if(retry === true || isclosed === true){
					for(var transport in subscriptionmessages){
						send(subscriptionmessages[transport]);
					}
				}
				isclosed = false;
			},
			getwebsocket = function(){
				if(websocket && !(websocket.readyState == WebSocket.CLOSING || websocket.readyState == WebSocket.CLOSED)) 
					return websocket;
				createwebsocket();
				return websocket;
			},
			send = function(msg, callback){
				if(!msg) return;
				var ws = getwebsocket(), transport = msg.TransportName;
				if(callback)
					callbacks[transport] = callback;
				subscriptionmessages[transport] = msg;
				if(ws.readyState != WebSocket.OPEN)
					queue.push(msg);
				else 
					ws.send(JSON.stringify(msg));
			},
			websocketretry = function(){
				running = false;
				if(autoreconnect === true){
					fn.log("Retrying websocket connection..");
					fn.onprotocolretry(websocket);
					createwebsocket(true);
				}
			},
			totalcallbacks = function(){
				var count = 0;
				for(var key in callbacks){
					count++;
				}
				return count;
			},
			createwebsocket = function(retry){
				websocket = new WebSocket(fn.setting.websocket());
				websocket.onopen = function () {
					websocket = this;
					websocketinit(retry);
					fn.onprotocolopen(websocket);
				};
				websocket.onmessage = function (e) {
					var msg = JSON.parse(e.data),
						transport = msg.Transport,
						callback = callbacks[transport],
						canclose = totalcallbacks() <= 1;

					//Extra internal message
					if(msg.Message) 
						msg = msg.Message;
					//Using psbka as KeepAlive in order to send less data
					if(msg.psbka){
						//Do nothing
						fn.log("Received Websocket KeepAlive for Message Listener");
						return;
					}

					if(msg.esbrn && msg.esbst){
						autoreconnect = false;
						if(fn.forceclose)
							fn.forceclose({reason: msg.esbrn, state: msg.esbst});
						close();
						return;
					}

					if(!callback) return;
					if(msg.NeedWebSocketRetry && autoreconnect === true) {
						if(subscriptionmessages[transport])
							subscriptionmessages[transport].RetryCount = msg.Count;
						fn.log("Reconnecting due to internal failure in 5 seconds...");
						fn.log("Failed Transport: " + msg.Transport);
						fn.log("Internal Reason: " + msg.Reason);
						setTimeout(function(){
							send(subscriptionmessages[transport]);
						}, 5000);
					}
					else if(msg.WebSocketRetryFailed){
						fn.log(String.format("Removing subscription[{0}] due to continuous retry...", transport));
						if(canclose)
							close();						
						fn.log(String.format("Please retry subscription for: {0}", transport));
						fn.log("Internal Reason: " + msg.Reason);
						
						if(canclose)
							fn.onprotocolretryfail(websocket);
					}
					else {
						//Async callback hack
						if(callback)
							setTimeout(function(){ callback(msg); }, 0);
					}
				};
				websocket.onerror = function(e){
					websocketretry();
				};
				websocket.onclose = function(e){
					fn.onprotocolclose(websocket);
					if(running !== false) {
						fn.log("Closing websocket connection...");
						websocketretry();
					}
				};
			},
			close = function(){
				if(!websocket) return;
				try{
					websocket.close();
				}catch(e){}
				isclosed = true;
				running = false;
			};
			return {
				connect: createwebsocket,
				send: send,
				close: close
			};
	})();

	var messagehandler = function (username, _transport, _interval, _batchsize, _action) {
		var subscriber = username,
			transport = _transport,
			interval = _interval,
			batchsize = _batchsize,
			action = _action,
			handle = null,
			hub = null,
			running = false,
			self = this,
			xhr = null,
			iswebsocket = false,
			websocketretrycount = 1,
			huburl = (endpointaddress + String.format("stream/?Subscriber={0}&TransportName={1}&BatchSize={2}&Interval={3}&ConnectionID={4}&durable={5}&ClientToken={6}",
				subscriber, transport, batchsize, interval, subscriber, durable, clienttoken)),
			handlemessage = function () {
				if (!running) return;
				xhr = connector.helper.invoke("NextMessages",
					{ Subscriber: subscriber, TransportName: transport,
						BatchSize: batchsize, ConnectionID: "longpolling", ClientToken: clienttoken
					}, function (msgs) {
						for (var i = 0; i < msgs.length; i++) {
							action(msgs[i]);
						}
						handle = setTimeout(handlemessage, interval);
					}, null, true);
			};
			

		longpolling = jQuery.browser.msie && parseInt(jQuery.browser.version) <= 7 ? true : longpolling;
		longpolling = jQuery.browser.opera && parseInt(jQuery.browser.version) < 12 && document.location.protocol.indexOf("file://") >= 0 ? true : longpolling;
		longpolling = jQuery.browser.opera && parseInt(jQuery.browser.version) < 12 && (huburl.indexOf("localhost") >= 0 || huburl.indexOf(String.format("{0}.", document.domain)) < 0) ? true : longpolling;


		this.start = function () {
			if (running) return;
			running = true;
			if (!fn.setting.protocol() && ("WebSocket" in window)) {
				iswebsocket = true;
				fn.websocketconnector.send({ SubscriberName: subscriber, TransportName: transport,
						BatchSize: batchsize, Interval: interval, UserName: apikey, Password: passcode,
						RetryCount: websocketretrycount, ClientToken: clienttoken
					}, action);
			}
			else if (!longpolling) {
				hub = new fn.hub();
				hub.onmessage = action;
				hub.onclose = function () { fn.onprotocolclose(hub); };
				hub.open(huburl);
			}
			else {
				handle = setTimeout(handlemessage, interval);
				fn.onprotocolopen({longpolling: true});
			}
		};
		this.stop = function () {
			running = false;
			if (longpolling) handle = clearTimeout(handle);
			try {
				if (hub) hub.close();
				else if (xhr) xhr.abort();
			} catch (e) { }
			xhr = null;
			hub = null;
		};
	};

	fn.setting = (function () {
		return {
			clienttoken: function(value) { clienttoken = value; },
			includehttpheaders: function(value) { includehttpheaders = value; },
			servertimeout: function(value) { servertimeout = value; },
			autoreconnect: function(value) { autoreconnect = value; },
			apikey: function (value) { apikey = value; },
			passcode: function (value) { passcode = value; },
			rethrow: function (value) { rethrow = value; },
			websocketport: function (port) { websocketport = port; },
			websocketrpc: function(value) { websocketrpc = value; },
			websocketsubscriptionport: function(port) { websocketsubscriptionport = port; },
			endpoint: function (value) { endpointaddress = value; },
			durable: function (value) { durable = value; },
			longpolling: function (value) { longpolling = value; },
			protocol: function(value){
				longpolling = value === fn.protocols.longpolling ? true : longpolling;
				if(value){
					protocol = value;
				} else {
					return protocol;
				}
			},
			logging: false,
			websocket: function () {
				if (websocketaddress) return websocketaddress;
				var url = endpointaddress.replace("//", "\\").split("/")[0],
					tokens = url.split(":");
				var secured = endpointaddress.indexOf("https:") >= 0;
				tokens[tokens.length - 1] = websocketport;
				tokens[0] = secured ? "wss" : "ws";
				url = tokens.join(":");
				return (websocketaddress = url.replace("\\", "//"));
			},
			websocketsubscription: function () {
				if (websocketsubscriptionaddress) return websocketsubscriptionaddress;
				var url = endpointaddress.replace("//", "\\").split("/")[0],
					tokens = url.split(":");
				var secured = endpointaddress.indexOf("https:") >= 0;
				tokens[tokens.length - 1] = websocketsubscriptionport;
				tokens[0] = secured ? "wss" : "ws";
				url = tokens.join(":");
				return (websocketsubscriptionaddress = url.replace("\\", "//"));
			},
			transport: (function () {
				var address = "endpoint://guest:guest@localhost:5672/",
					parseEndpoint = function (topicName, subscriberName) {
						return address + topicName + subscriberName;
					},
					transporttype = fn.transport.rabbitmq,
					transportformat = fn.format.xml;
				return {
					address: function (value) { address = value; },
					format: function(value) { transportformat = value; },
					transporttype: function() { return transporttype; },
					type: function(value) { transporttype = value; },
					info: function (topicName, subscriberName) {
						switch (transporttype) {
							case fn.transport.msmq:
							case fn.transport.rabbitmq:
							case fn.transport.redis:
								return {
									Format: transportformat,
									Path: parseEndpoint(topicName, subscriberName)
								};
							case fn.transport.tcp:
								var tokens = address.split(':'),
									useSSL = tokens.length > 2 ?
										("true" === tokens[2].toLowerCase) : false,
									ipAddress = tokens[0],
									port = parseInt(tokens[1]);
								return {
									Format: transportformat,
									IPAddress: ipAddress, Port: port,
									UseSSL: useSSL
								};
						}
					}
				};
			})()
		};
	})();

	fn.register = function (data) {
		if (!data.topic)
			throw "topic need to be set for register method";
		var topic = data.topic;
		//return if topic already exists to reduce extra connector calls since topic is not volatile
		if(topics[topic]) {
			return;
		}
		connector.helper.invoke("RegisterTopic", { 
				Name: topic,
				Description: data.description || topic,
				Contract: data.info || {}
		});
		topics[topic] = topic;
	};
	fn.unregister = function (data) {
		if(!data.topic)
			throw "topic need to be set for unregister method";
		var topic = data.topic;
		connector.helper.invoke("DeleteTopic", {name: topic});
		if(topics[topic]){
			delete topics[topic];
		}
	};
	fn.ping = function(callback){
		connector.helper.invoke("Ping", {}, function(result){
			if(callback) callback({success: result, source: "success-callback"});
		}, function(e) {
			if(callback) callback({success: false, source: "error-callback"});
		});
	};
	fn.publish = function (data) {
		if (!data.topic || !data.message)
			throw "topic and message property need to be set for publish method";
		//Register topic if not exist
		fn.register({topic: data.topic});
		var groupid = data.groupid,
			sequenceid = data.sequenceid,
			message = data.message,
			headers = data.headers || {},
			expiration = data.expiration || fn.timespan(30)/*30 days*/;
		if(groupid && sequenceid){
			headers["ESB_GROUP_ID"] = groupid;
			headers["ESB_SEQUENCE_ID"] = sequenceid;
		}

		fn.log('publishing to "' + data.topic + '" with data ', data.message);

		connector.helper.invoke("PublishTopic",
			{ 
				Topic: data.topic,
				ExpiresIn: expiration,
				Headers: headers,
				Messages: (message instanceof Array) ? message : [message]
			}, null, null, true);
	};
	fn.unsubscribe = function (data) {
		if (!data.topic)
			throw "topic property need to be set for unsubscribe method";
		var topicname = data.topic,
			name = getusername(),
			done = data.done;
		fn.log('unsubscribing to "' + data.topic + '"');
		connector.helper.invoke("UnSubscribe",
			 { Subscriber: name, Topic: topicname, Transport: { Name: topicname } }, function (_) {
				 fn.log('unsubscribed from "' + data.topic + '"');
				if(done) done();
			 });
		if (handlers[topicname]){
			handlers[topicname].stop();
			delete handlers[topicname];
		}
	};

	fn.issubscribe = function(data){
		if(!data.topic)
			throw "topic property need to be set to check for subscription";
		return !handlers[data.topic] === false;
	};

	fn.update = function(data){
		if(!data.topic)
			throw "topic property need to be set";
		if(!data.filter){
			if(console.warn){
				var warning = String.format("filter property is not set. empty filter means all messages for [{0}] will be received",
					 data.topic);
				console.warn(warning);
			}
		}
		var done = data.done;
		//Register topic if not exist
		fn.register({topic: data.topic});
		var topicname = data.topic,
			username = getusername(),
			filter = data.filter || "",
			casesensitive = data.casesensitive == undefined ? true : data.casesensitive,
			needheader = data.needheader == undefined ? false : data.needheader;

		fn.log('updating "' + topicname + '" with filter "' + filter + '"');

		connector.helper.invoke("Update",
			  {Subscriber: username, Topic: topicname, Filter: filter, 
			  NeedHeader: needheader, CaseSensitive: casesensitive
			  }, function (_) {
					fn.log('updated "' + data.topic + '"');
					if (done)
						done();
				});
	};

	fn.subscribe = function (data) {
		if (!data.topic || !data.callback)
			throw "topic and callback property need to be set for subscribe method";
		if (typeof (data.callback) !== "function")
			throw "callback must be a function";
		//Register topic if not exist
		fn.register({topic: data.topic});
		var topicname = data.topic,
			i,
			callback = data.callback,
			username = getusername(),
			filter = data.filter || "",
			casesensitive = data.casesensitive == undefined ? true : data.casesensitive,
			needheader = data.needheader == undefined ? false : data.needheader,
			batchsize = data.batchsize || 1,
			done = data.done,
			interval = data.interval || fn.timespan(0, 0, 0, 0, 5) /*5 millisecond*/,
			handler;

		fn.log('subscribing to "' + topicname + '" with filter "' + filter + '"');

		connector.helper.invoke("Subscribe", { 
			  Subscriber: username, 
			  Transport: { 
				Name: topicname,
				TypeID: fn.setting.transport.transporttype(),
				Parameters: fn.setting.transport.info(topicname, username)
			  },
			  Topic: topicname,
			  Filter: filter, NeedHeader: needheader, CaseSensitive: casesensitive
		}, function(_){
			var oldcallback = function(msg){
				fn.log('received "'+data.topic+'" with data ', msg);
			   callback(msg); 
			};
			requires(function(){
				handler = new messagehandler(username, topicname, interval, 1, oldcallback);
				handler.start();
				handlers[topicname] = handler;
				if (done) {
					fn.log('subscribed "' + data.topic + '"');
					done();
				}
		   });
		});
	};
	if(parseInt(jQuery.browser.version) == 9)
		bindevent(window, "message", handleframe);
   
	var isdisconnect = false;
	function disconnectserver(){
		if(isdisconnect === true) {
			return;
		}
		var username = getusername();
		if (fn.ondisconnect) fn.ondisconnect();
		if(fn.ondelete) fn.ondelete();
		if (!durable) {
			connector.helper.invoke("DeleteSubscriber", { name: username });
			setstorage(usernamekey, "", -1);
		}
		for (var key in handlers){
			var handler = handlers[key];
			if(!handler) continue;
			handler.stop();
			delete handlers[key];
		}
		handlers = {};
		framehandlers = {};
		//Disconnect connection to esb server and any streaming connections
		connector.helper.invoke("Disconnect", {name: username});
		isdisconnect = true;
	}

	jQuery(window).bind("beforeunload", function () {
		disconnectserver();
	});

	//Load flashsocket if needed
	requires();
} (PSB));